package io.github.generator;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.type.ITypeConvertHandler;
import io.github.common.entity.dto.AbstractDTO;
import io.github.common.entity.po.AbstractPo;
import io.github.common.entity.req.AbstractForm;
import io.github.common.entity.req.AbstractReq;
import io.github.common.util.StringUtils;
import io.github.generator.engine.ApplicationVelocityEngine;
import io.github.generator.strategy.CustomFileInjectionStrategy;
import io.github.generator.strategy.InjectionConfigStrategy;
import io.github.generator.strategy.Strategy;
import io.github.generator.util.GeneratorUtils;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 代码生成器
 *
 * @author haijun
 * @version 1.0.0
 * @email "mailto:1424132610@qq.com"
 * @date 2024.11.06 20:32
 * @since 1.0.0
 */
@Data
@Accessors(chain = true)
public class CodeGenerator implements Generator {

    /**
     * Author
     */
    private String author = System.getProperty("user.name");
    /**
     * Email
     */
    private String email = System.getProperty("user.email");
    /**
     * Url
     */
    private String url;
    /**
     * Username
     */
    private String username;
    /**
     * Password
     */
    private String password;

    /**
     * Table prefix
     */
    private String tablePrefix;

    /**
     * Table names
     */
    private String[] tableNames;

    /**
     * Clazz
     */
    private Class<?> mainApplicationClass;

    /**
     * Model package path
     */
    private String modelPackagePath;

    /**
     * Supper mapper class
     */
    private Class<?> supperMapperClass = BaseMapper.class;

    /**
     * Supper service
     */
    private Class<?> supperService = IService.class;

    /**
     * Supper service
     */
    private Class<?> supperServiceImpl = ServiceImpl.class;

    /**
     * Base req
     */
    private Class<?> baseReq = AbstractReq.class;

    /**
     * Base form
     */
    private Class<?> baseForm = AbstractForm.class;

    /**
     * Base dto
     */
    private Class<?> baseDTO = AbstractDTO.class;

    /**
     * Base entity
     */
    private Class<?> baseEntity = AbstractPo.class;

    /**
     * Template path
     */
    private String templatePath;

    /**
     * Excluded fields
     */
    private List<String> excludedFields = Arrays.asList("id", "create_time", "update_time", "deleted");

    /**
     * Type convert handlers
     */
    private ITypeConvertHandler typeConvertHandler;

    /**
     * Strategies
     */
    private final List<Strategy> strategies = new ArrayList<>();


    /**
     * Template switch
     */
    private final TemplateSwitch templateSwitch = new TemplateSwitch();

    /**
     * Init strategy
     *
     * @since 1.0.0
     */
    private void initStrategy() {
        this.strategies.add(new CustomFileInjectionStrategy(this.templateSwitch));
        this.strategies.add(new InjectionConfigStrategy());
    }

    /**
     * Build
     *
     * @since 1.0.0
     */
    @Override
    public void execute() {
        this.initStrategy();
        FastAutoGenerator
                .create(this.url, this.username, this.password)
                .globalConfig(builder -> {
                    builder.author(this.author)
                            .enableSwagger()
                            .disableOpenDir()
                            .outputDir(GeneratorUtils.getProjectRoot("src", "main", "java"));
                })
                .dataSourceConfig(builder -> {
                    if (this.typeConvertHandler != null) {
                        builder.typeConvertHandler(this.typeConvertHandler);
                    }
                })
                .packageConfig(builder -> {
                    builder.parent(GeneratorUtils.getParentPackagePath(this.mainApplicationClass))
                            .entity(GeneratorUtils.getPackagePath("entity", this.modelPackagePath, "po"))
                            .controller(GeneratorUtils.getPackagePath("controller", this.modelPackagePath))
                            .service(GeneratorUtils.getPackagePath("service"))
                            .serviceImpl(GeneratorUtils.getPackagePath("service", "impl"))
                            .mapper(GeneratorUtils.getPackagePath("mapper", this.modelPackagePath))
                            .pathInfo(Collections.singletonMap(OutputFile.xml, GeneratorUtils.getXmlFilePath()));
                })
                .injectionConfig(builder -> this.strategies.forEach(strategy -> strategy.builder(this, builder)))
                .strategyConfig(builder -> {
                    builder.addInclude(this.tableNames);
                    if (StringUtils.isNotBlank(this.tablePrefix)) {
                        builder.addTablePrefix(this.tablePrefix);
                    }
                    if (this.templateSwitch.disableEntity) {
                        builder.entityBuilder().disable();
                    }
                    if (this.templateSwitch.disableMapper) {
                        builder.mapperBuilder().disable();
                    }
                    if (this.templateSwitch.disableService) {
                        builder.serviceBuilder().disable();
                    }
                    if (this.templateSwitch.disableServiceImpl) {
                        builder.serviceBuilder().disableServiceImpl();
                    }
                    if (this.templateSwitch.disableMapperXml) {
                        builder.mapperBuilder().disableMapperXml();
                    }
                    if (this.templateSwitch.disableController) {
                        builder.controllerBuilder().disable();
                    }
                    if (!this.templateSwitch.disableLombok) {
                        builder.entityBuilder().enableLombok();
                    }
                    builder.entityBuilder()
                            .javaTemplate(GeneratorUtils.getTemplateFilePath(this.templatePath, "entity.java"))
                            .mapperBuilder()
                            .mapperTemplate(GeneratorUtils.getTemplateFilePath(this.templatePath, "mapper.java"))
                            .superClass(this.supperMapperClass)
                            .mapperXmlTemplate(GeneratorUtils.getTemplateFilePath(this.templatePath, "mapper.xml"))
                            .serviceBuilder()
                            .serviceTemplate(GeneratorUtils.getTemplateFilePath(this.templatePath, "service.java"))
                            .superServiceClass(this.supperService)
                            .convertServiceFileName(entityName -> entityName + "Service")
                            .serviceImplTemplate(GeneratorUtils.getTemplateFilePath(this.templatePath, "service.impl.java"))
                            .superServiceImplClass(this.supperServiceImpl)
                            .controllerBuilder()
                            .template(GeneratorUtils.getTemplateFilePath(this.templatePath, "controller.java"))
                            .build();
                })
                .templateEngine(new ApplicationVelocityEngine())
                .execute();
    }

    /**
     * Builder
     *
     * @return the code generator
     * @since 1.0.0
     */
    public static CodeGenerator builder() {
        return new CodeGenerator();
    }

    /**
     * Author
     *
     * @param author author
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator author(String author) {
        this.author = author;
        return this;
    }

    /**
     * Email
     *
     * @param email email
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator email(String email) {
        this.email = email;
        return this;
    }

    /**
     * Url
     *
     * @param url url
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator url(String url) {
        this.url = url;
        return this;
    }

    /**
     * Username
     *
     * @param username username
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator username(String username) {
        this.username = username;
        return this;
    }

    /**
     * Password
     *
     * @param password password
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator password(String password) {
        this.password = password;
        return this;
    }

    /**
     * Table prefix
     *
     * @param tablePrefix table prefix
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator tablePrefix(String tablePrefix) {
        this.tablePrefix = tablePrefix;
        return this;
    }

    /**
     * Table names
     *
     * @param tableNames table names
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator tableNames(String... tableNames) {
        this.tableNames = tableNames;
        return this;
    }

    /**
     * Main application class
     *
     * @param mainApplicationClass main application class
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator mainApplicationClass(Class<?> mainApplicationClass) {
        this.mainApplicationClass = mainApplicationClass;
        return this;
    }

    /**
     * Model package path
     *
     * @param modelPackagePath model package path
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator modelPackagePath(String modelPackagePath) {
        this.modelPackagePath = modelPackagePath;
        return this;
    }

    /**
     * Supper mapper class
     *
     * @param supperMapperClass supper mapper class
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator supperMapperClass(Class<?> supperMapperClass) {
        this.supperMapperClass = supperMapperClass;
        return this;
    }

    /**
     * Supper service
     *
     * @param supperService supper service
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator supperService(Class<?> supperService) {
        this.supperService = supperService;
        return this;
    }

    /**
     * Supper service
     *
     * @param supperServiceImpl supper service
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator supperServiceImpl(Class<?> supperServiceImpl) {
        this.supperServiceImpl = supperServiceImpl;
        return this;
    }

    /**
     * Base req
     *
     * @param baseReq base req
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator baseReq(Class<?> baseReq) {
        this.baseReq = baseReq;
        return this;
    }

    /**
     * Base form
     *
     * @param baseForm base form
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator baseForm(Class<?> baseForm) {
        this.baseForm = baseForm;
        return this;
    }

    /**
     * Base dto
     *
     * @param baseDTO base dto
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator baseDTO(Class<?> baseDTO) {
        this.baseDTO = baseDTO;
        return this;
    }

    /**
     * Base entity
     *
     * @param baseEntity base entity
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator baseEntity(Class<?> baseEntity) {
        this.baseEntity = baseEntity;
        return this;
    }

    /**
     * Template path
     *
     * @param templatePath template path
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator templatePath(String templatePath) {
        this.templatePath = templatePath;
        return this;
    }

    /**
     * Excluded fields
     *
     * @param excludedFields excluded fields
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator excludedFields(String... excludedFields) {
        this.excludedFields = Arrays.asList(excludedFields);
        return this;
    }

    /**
     * Type convert handler
     *
     * @param typeConvertHandler type convert handler
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator typeConvertHandler(ITypeConvertHandler typeConvertHandler) {
        this.typeConvertHandler = typeConvertHandler;
        return this;
    }

    /**
     * Add strategy
     *
     * @param strategy strategy
     * @return the code generator
     * @since 1.0.0
     */
    public CodeGenerator addStrategy(Strategy strategy) {
        this.strategies.add(strategy);
        return this;
    }

    /**
     * Template switch
     *
     * @return the template switch
     * @since 1.0.0
     */
    public TemplateSwitch templateSwitch() {
        return this.templateSwitch;
    }


    /**
     * 模板开关
     *
     * @author ZH.Team
     * @version 1.0.0
     * @email "mailto:ZH.Team@zhxx.com"
     * @date 2024.11.20 20:19
     * @since 1.0.0
     */
    @Data
    public class TemplateSwitch {

        /**
         * Disable entity
         */
        private boolean disableEntity;

        /**
         * Disable mapper xml
         */
        private boolean disableMapperXml;

        /**
         * Disable mapper
         */
        private boolean disableMapper;

        /**
         * Disable service
         */
        private boolean disableService;

        /**
         * Disable service
         */
        private boolean disableServiceImpl;

        /**
         * Disable controller
         */
        private boolean disableController;

        /**
         * Disable lombok
         */
        private boolean disableLombok;

        /**
         * Disable dto
         */
        private boolean disableDto;

        /** Disable enum */
        private boolean disableEnum;

        /**
         * Disable req
         */
        private boolean disableReq;

        /**
         * Disable form
         */
        private boolean disableForm;

        /**
         * Disable entity
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableEntity() {
            this.disableEntity = true;
            return this;
        }

        /**
         * Disable mapper
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableMapper() {
            this.disableMapper = true;
            return this;
        }

        /**
         * Disable service
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableService() {
            this.disableService = true;
            return this;
        }

        /**
         * Disable service
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableServiceImpl() {
            this.disableServiceImpl = true;
            return this;
        }

        /**
         * Disable mapper xml
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableMapperXml() {
            this.disableMapperXml = true;
            return this;
        }

        /**
         * Disable controller
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableController() {
            this.disableController = true;
            return this;
        }

        /**
         * Enable dto
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableDto() {
            this.disableDto = true;
            return this;
        }

        /**
         * Disable req
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableReq() {
            this.disableReq = true;
            return this;
        }

        /**
         * Disable form
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableForm() {
            this.disableForm = true;
            return this;
        }

        /**
         * Disable enum
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableEnum() {
            this.disableEnum = true;
            return this;
        }
        /**
         * Disable lombok
         *
         * @return the template switch
         * @since 1.0.0
         */
        public TemplateSwitch disableLombok() {
            this.disableLombok = true;
            return this;
        }

        /**
         * Build
         *
         * @return the code generator
         * @since 1.0.0
         */
        public CodeGenerator build() {
            return CodeGenerator.this;
        }

    }
}
